Clear start animation time after SeekableTransitionState animates/snaps SeekableTransitionState updates its Transition's start time, but doesn't clear it after animations finish (or a snap completes). Since Transition::isRunning checks whether its start time is set, it always returned true after a SeekableTransitionState ran any animation, even after that animation finished. This fixes it by clearing the transition's start time after animations/snaps complete. Bug: 334275648 Test: new tests added Change-Id: I20ba0e12296d890c9d89b2fd4c5372ff0b3b10a9
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt index f2101f2..d5992f9 100644 --- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt +++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/SeekableTransitionStateTest.kt
@@ -51,6 +51,7 @@ import androidx.test.filters.SdkSuppress import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -2390,4 +2391,81 @@ } } } + + @Test + fun isRunningDuringAnimateTo() { + val seekableTransitionState = SeekableTransitionState(AnimStates.From) + lateinit var transition: Transition<AnimStates> + var animatedValue by mutableIntStateOf(-1) + + rule.mainClock.autoAdvance = false + + rule.setContent { + LaunchedEffect(seekableTransitionState) { + seekableTransitionState.animateTo(AnimStates.To) + } + transition = rememberTransition(seekableTransitionState, label = "Test") + animatedValue = transition.animateInt( + label = "Value", + transitionSpec = { tween(easing = LinearEasing) } + ) { state -> + when (state) { + AnimStates.From -> 0 + else -> 1000 + } + }.value + } + rule.runOnIdle { + assertEquals(0, animatedValue) + assertFalse(transition.isRunning) + } + rule.mainClock.advanceTimeByFrame() // wait for composition after animateTo() + rule.mainClock.advanceTimeByFrame() // one frame to set the start time + rule.runOnIdle { + assertTrue(animatedValue > 0) + assertTrue(transition.isRunning) + } + rule.mainClock.advanceTimeBy(5000) + rule.runOnIdle { + assertEquals(1000, animatedValue) + assertFalse(transition.isRunning) + } + } + + @Test + fun isRunningFalseAfterSnapTo() { + val seekableTransitionState = SeekableTransitionState(AnimStates.From) + lateinit var transition: Transition<AnimStates> + var animatedValue by mutableIntStateOf(-1) + + rule.mainClock.autoAdvance = false + + rule.setContent { + LaunchedEffect(seekableTransitionState) { + awaitFrame() // Not sure why this is needed. Animated val doesn't change without it. + seekableTransitionState.snapTo(AnimStates.To) + } + transition = rememberTransition(seekableTransitionState, label = "Test") + animatedValue = transition.animateInt( + label = "Value", + transitionSpec = { tween(easing = LinearEasing) } + ) { state -> + when (state) { + AnimStates.From -> 0 + else -> 1000 + } + }.value + } + rule.runOnIdle { + assertEquals(0, animatedValue) + assertFalse(transition.isRunning) + } + rule.mainClock.advanceTimeByFrame() // wait for composition after animateTo() + rule.mainClock.advanceTimeByFrame() // one frame to snap + rule.mainClock.advanceTimeByFrame() // one frame for LaunchedEffect's awaitFrame() + rule.runOnIdle { + assertEquals(1000, animatedValue) + assertFalse(transition.isRunning) + } + } } diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt index 111f3e9..837cb30 100644 --- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt +++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -471,6 +471,7 @@ // the correct animation values waitForCompositionAfterTargetStateChange() } + transition.onTransitionEnd() } } @@ -685,6 +686,7 @@ currentState = targetState waitForComposition() fraction = 0f + transition.onTransitionEnd() } } }